<!doctype html>
<html>
<head>
<title>ShinyTouch/JS</title>
<script type="text/javascript" src="perspective.js"></script>
<script type="text/javascript">
/* requestAnimationFrame polyfill */
(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = 
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());



/* back to antimatter15's code */    
    
      ////////////////Misc Configuration/////////////////
      var min_target_size = 2;
      var max_matches = 3;
      
      ///////////////Hue Detector Configuration//////////
      var reflect_offset = [-5, 0];
      var background_offset = [5, -10];
      var reflect_range = 25;
      
      ////////////////Target Color Configuration/////////

      var tgt = {
        //r: [226,256], //red
        //g: [146,256], //green
        //b: [137,256]  //bluu
        //r: [160,190],
        //g: [95,115],
        //b: [100,130]
        r: [110,140],
        g: [70,90],
        b: [60,90]
      }

      ////////////////Quad Configuration/////////////////    
      var quad = {
        //tl: [67,83], //top left
        //bl: [67,277], //bottom left
        //tr: [151,8], //top right
        //br: [151,283], //bottom right
        tl: [68,16],
        bl: [78,268],
        //bl: [68,264],
        tr: [148,48],
        br: [148,176]
      }
      ////////////////Quad Generation (Not Config)////////

      
      quad.left = Math.min(quad.tl[0],quad.bl[0]); // (quad.tl[0]+quad.bl[0])/2 //take average
      quad.right = Math.max(quad.tr[0],quad.br[0]); // (quad.tr[0]+quad.br[0])/2 //take average
      quad.width = quad.right-quad.left; //width of quad

      quad.top = Math.min(quad.tl[1],quad.tr[1]);
      quad.bottom = Math.max(quad.bl[1],quad.br[1]);

      quad.height = quad.bottom - quad.top; // quad.br[1] - quad.tr[1]
      
      quad.toprate = (quad.tl[1]-quad.tr[1])/quad.width; //rate of top change
      quad.bottomrate = (quad.bl[1]-quad.br[1])/quad.width; //rate of down change
      
      transform = new Perspective();
      transform.setdst([0,0],[160,0],[0,120],[160,120]);
      transform.setsrc(quad.tl, quad.tr, quad.bl, quad.br);

      
      /////////////////Random Variables///////////////////
      
      var current_fps = 0;
      var last_time = 0;
      var video = null;
      
      /////////////////Yay for Actual Code Time///////////
      
      function in_range(num, range){
        return num >= range[0] && num <= range[1]
      }
      
      function colorTargetMatch(color){
        return in_range(color[0], tgt.r) &&
          in_range(color[1], tgt.g) &&
          in_range(color[2], tgt.b)
      }
      
      function getpix(pix, x, y){
        var index = Math.round(4 * (x + Math.round(y)  * quad.width));
        return [pix[index], pix[index+1], pix[index+2]];
      }
      
      function draw(){
        var canvas = document.getElementById('output');
        var ctx = canvas.getContext('2d');
        
        var drawcanvas = document.getElementById('draw');
        var drawctx = drawcanvas.getContext('2d');
        
        ctx.clearRect(0,0,quad.width, quad.height)
        
        ////////////////Draw the magical image input////////////////////
        ctx.drawImage(video,-quad.left,-quad.top); //-quad.tr[1]);
        
                
        ////////////////Draw Trapezoid Around Target Area///////////////
        ctx.strokeStyle = '#0ff';
        ctx.lineWidth   = 1;
        ctx.beginPath();
        ctx.moveTo(quad.tl[0]-quad.left, quad.tl[1]-quad.top);
        ctx.lineTo(quad.bl[0]-quad.left, quad.bl[1]-quad.top);
        ctx.lineTo(quad.br[0]-quad.left, quad.br[1]-quad.top);
        ctx.lineTo(quad.tr[0]-quad.left, quad.tr[1]-quad.top); //yes, this is always tzero
        ctx.lineTo(quad.tl[0]-quad.left, quad.tl[1]-quad.top);
        ctx.stroke();
        ctx.closePath()

        ///////////////Scan the magical box/////////////////
        var global_matches = 0;
        var imgd = ctx.getImageData(0, 0, quad.width, quad.height);
        var pix = imgd.data;
        for(var x = quad.width; x > 0; x--){
          var matches = 0; //consecutive matches

          for(var y = quad.tl[1] - quad.toprate * x -quad.tr[1]; //start at top of quad section
            y < quad.bl[1] - quad.bottomrate * x -quad.tr[1]; //end at bottom of quad section
            y++) //increment y

          { //start y loop
            
            var color = getpix(pix, x, y);
            
            if(colorTargetMatch(color)){
              matches++
              
              ctx.fillStyle = "rgb(0,255,0)";
              ctx.fillRect(x, y, 1, 1)
            }else{
              if(matches > min_target_size){ // did we just past a block of a good number of matches?
                ///////////////////The following is quite a hackish algorithm//////////////////////
                ///////////////////Not elitist enough to figure out a better way///////////////////
                

                // hit the average of the matches
                // assuming that the matches we passed were a block above right now
                var yc = y - matches/2;
                
                ctx.fillStyle = "rgb(255,0,0)";
                ctx.fillRect(x, yc, 1, 1)
                
                var reflection = getpix(pix, x+reflect_offset[0], yc+reflect_offset[1]);		// the pixel in the reflection direction
                var background = getpix(pix, x+background_offset[0], yc+background_offset[1]);	// same, for a bg color

                var reflection_hue = rgbToHue(reflection[0], reflection[1], reflection[2]) * 10;
                var background_hue = rgbToHue(background[0], background[1], background[2]) * 10;
                
                var hue_diff = Math.abs(background_hue - reflection_hue); //absolute difference

				// some random colors in the top
                
                ctx.fillStyle = "rgb(255,0,0)";
                ctx.fillRect(x, 0, 1, hue_diff)
                
                ctx.fillStyle = "rgb(0,0,255)";
                ctx.fillRect(x, reflect_range, 1, 1)
                
                ctx.fillStyle = "rgb("+reflection.join(",")+")";
                ctx.fillRect(x, 0, 1, 1)
                
                ctx.fillStyle = "rgb("+background.join(",")+")";
                ctx.fillRect(x, 1, 1, 1)
                
                if(!colorTargetMatch(reflection) &&
                  !colorTargetMatch(background) &&
                  hue_diff > reflect_range  && 
                  x + reflect_offset[0] <= quad.width){ //if it's a color match!
                  
                  ctx.strokeStyle = "rgb(0,255,0)";
                  ctx.strokeRect(x-10, yc-10, 20, 20)
                
                  ctx.fillStyle = "rgb(0,255,0)";
                  ctx.fillRect(x, reflect_range, 1, 1)
                  
                  ctx.fillStyle = "rgb(255,255,0)";
                  ctx.fillRect(x+reflect_offset[0], yc+reflect_offset[1], 1, 1)
                  
                  ctx.fillStyle = "rgb(255,0,255)";
                  ctx.fillRect(x+background_offset[0], yc+background_offset[1], 1, 1)
                  
                  var newcoords = transform.warp(x+quad.left, yc+quad.tr[1])
                  drawctx.strokeStyle = "rgb(0,255,0)"
                  drawctx.strokeRect(newcoords[0]-2,newcoords[1]-2, 4, 4);
                  
                  x = -1; //this is how i end the loop
                  y = 1337; //um, maybe g(64) would be bigger.
                  
                }
                
                if(++global_matches > max_matches){
                  x = -1; //this is how i end the loop
                  y = 1337; //um, maybe g(64) would be bigger.
                }
                
              }
              matches = 0;
            }
          } //end y loop
        } //end x loop
        
        current_fps = Math.round(1000/(new Date() - last_time));
        last_time = +new Date();
        
        ctx.fillStyle = "rgb(255,255,255)";
        //if(ctx.fillText){
          ctx.fillText("FPS: "+current_fps, 5, quad.height - 10);
        //}else{
        //  ctx.translate(5, quad.height - 10);
        //  ctx.mozDrawText("FPS: "+current_fps);
        //  ctx.translate(-5, -(quad.height - 10));
        //}
        // setTimeout(draw, 0)
        window.requestAnimationFrame(draw);
      }
      
      function rgbToHue(r, g, b){
        r /= 255, g /= 255, b /= 255;
        var max = Math.max(r, g, b), 
            min = Math.min(r, g, b), 
            d = max - min;
        switch(max){
          case min: return 0;
          case r: return (g - b) / d + (g < b ? 6 : 0);
          case g: return (b - r) / d + 2;
          case b: return (r - g) / d + 4;
        }
      }
      
      
      window.onload=function(){
        window.video = document.getElementById("input")
      
        var canvas = document.getElementById('output');

        canvas.addEventListener("click", function(event){
          var ctx = canvas.getContext('2d');
          var imgd = ctx.getImageData(event.clientX, event.clientY, quad.width, quad.height);
          alert([imgd.data[0],imgd.data[1],imgd.data[2]].join(","))
        //  alert([event.clientX,event.clientY]);
        },true)

        canvas.width = quad.width;
        canvas.height = quad.height;
        
        loadVideo();
        // draw();

      }
      
      
function hexToRgb(hex) {
  hex = hex.replace(/^#/,"").toUpperCase();
  if (!(/^[0-9A-F]{6}$/gm).test(hex)) {
    return null;
  }
  var r = parseInt(hex.substring(0,2),16);
  var g = parseInt(hex.substring(2,4),16);
  var b = parseInt(hex.substring(4,6),16);
  return [r,g,b];
}
function rgbToHex(r,g,b,pound) {
  r = (r & 0xff).toString(16);
  g = (g & 0xff).toString(16);
  b = (b & 0xff).toString(16);

  if (r.length<2) r="0"+r;
  if (g.length<2) g="0"+g;
  if (b.length<2) b="0"+b;

  return (pound?"#":"") + r + g + b;
}
function loadVideo() {
  if (!video) return;

  navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;

  navigator.getUserMedia("video",function(stream){
    //try { video.src = window.URL.createObjectURL(stream); } catch(e){
    	//try { video.src = window.webkitURL.createObjectURL(stream); }catch(e){
   			video.src = stream;
   		//}
   	//}
  },function(e) {
    alert("getUserMedia error: "+e.code);console.log(e);
  });
}
function calibrate() {
  window.open("calibrate.html","calibrate","toolbar=no");
}
var pixelImageData = [[],[],[],[]];
var avgPixelImageData = [];
function calibrateSnapshot(i,timeLeft) {
  var canvas = document.createElement("canvas");
  canvas.height = video.videoHeight;
  canvas.width = video.videoWidth;
  var ctx = canvas.getContext("2d");
  ctx.drawImage(video,0,0);
  
  var image = new Image();
  image.src=canvas.toDataURL();
  document.getElementById("content").appendChild(image);

  pixelImageData[i].push(canvas.getContext("2d").getImageData(0,0,canvas.width,canvas.height));
}
function averageImageData(i1,i2) {
  for(var i=0,len=i1.width*i1.height*4;i<len;i+=4){
    i1.data[i]=Math.ceil((i1.data[i]+i2.data[i])/2);
    i1.data[i+1]=Math.ceil((i1.data[i+1]+i2.data[i+1])/2);
    i1.data[i+2]=Math.ceil((i1.data[i+2]+i2.data[i+2])/2);
  }
  return i1;
}
function diffImageData(i1,i2) {
  
  //var out = new Uint8Array(3);
  for (var i=0,len=i1.height*i1.width*4;i<len;i+=4){
    i1.data[i]=Math.abs(i1.data[i]-i2.data[i]);
    i1.data[i+1]=Math.abs(i1.data[i+1]-i2.data[i+1]);
    i1.data[i+2]=Math.abs(i1.data[i+2]-i2.data[i+2]);

    //i1.data[i]=out[0];
    //i1.data[i+1]=out[1];
    //i1.data[i+2]=out[2];
  }
  return i1;
}
function processCalibration(calibrations) {
  pixelImageData.forEach(function(e,i) {
    printImageData(avgPixelImageData[i]=averageImageData(e[0],e[1]));
  });


  /*var imageDiffs = [
    diffImageData(avgPixelImageData[0],avgPixelImageData[1]), // #00FFFF
    //diffImageData(avgPixelImageData[0],avgPixelImageData[2]),
    //diffImageData(avgPixelImageData[0],avgPixelImageData[3]),
    diffImageData(avgPixelImageData[1],avgPixelImageData[2]), // #FFFF00
    //diffImageData(avgPixelImageData[1],avgPixelImageData[3]),
    diffImageData(avgPixelImageData[2],avgPixelImageData[3])  // #00FF00
  ];
  */
  var imageDiffs = [[0,1],[1,2],[2,3]];
  imageDiffs.forEach(function(el) {
    var imageDiff = diffImageData(avgPixelImageData[el[0]],avgPixelImageData[el[1]]);

    var color1 = hexToRgb(calibrations[el[0]][0]), color2 = hexToRgb(calibrations[el[1]][0]);

    // The following is dependent on the fact that each is either FF or 00
    var finalColor = [color1[0]-color2[0],color1[1]-color2[1],color1[2]-color2[2]];
    finalColor.forEach(function(e){
      e = !!Math.abs(e);
    });

    //showWhiteBlack(el);
    showFitsRatio(imageDiff,finalColor[0],finalColor[1],finalColor[2]); //WEIRD function names...
    findCoords(imageDiff,finalColor[0],finalColor[1],finalColor[2]);
    printImageData(imageDiff);
  });
}
var IS_BLACK = 6; //threshhold for if r,g,b are below this it's black
function showFitsRatio(image,r,g,b) { //r, g, b should be 1 or 0
  r = !r;
  g = !g;
  b = !b;
  var count = r+g+b;

  for (var i=0,len=image.width*image.height*4;i<len;i+=4){
    var curR = image.data[i], curG = image.data[i+1], curB = image.data[i+2];
    if(curR+curG+curB < 3*IS_BLACK) {
      image.data[i]=255;image.data[i+1]=image.data[i+2]=0;
    } //umm...
    else if((curR*!r+curG*!g+curB*!b)*(3-count) > (curR*r+curG*g+curB*b)*count) {
      // ^ fail.
      // this is a pretty fail not-an-algorithm...
      image.data[i]=image.data[i+1]=0;image.data[i+2]=255;
    }
  }
}
/* So, this is a TERRIBLE, TERRIBLE, UNOPTIMIZED algorithm.
 * Basically, you need to know the expected color difference first.
 * r,g,b are all either 0 or 255 (fail!!!) It takes that and for 
 * every pixel checks if it's the right color. The showFitsRatio
 * function paints red all black pixels (determined if the average
 * of r,g,b is below a threshhold) and bluu all the ones that fit 
 * the ratio. It checks which ones are closest to the corners, and
 * for every one, makes sure that PIXEL_OFFSET to the left or right
 * that one also fits the color ratio (to prevent camera noise
 * from being chosen).
 */
// This currently is broken. btw
var PIXEL_OFFSET = 5; //how many pixels away to look
function findCoords(image,r,g,b) { //descendant of showFitsRatio
  var tl=[-1,Infinity],
      br=[-1,0],
      tr=[-1,Infinity],
      bl=[-1,0];

  var iD = image.data;
  for (var i=0,len=image.width*image.height*4;i<len;i+=4){
    var curR = iD[i], curG = iD[i+1], curB = iD[i+2];
    if(curR+curG+curB < 3*IS_BLACK) {
      // this pixel is black, ignore it
    } //umm...
    else if(weirdRatioFormula(curR,curG,curB,r,g,b,count)) {
      //this pixel is the color we are looking for
      //BUT, is it just noise?


      var x;

      x=distFromCorner(i,image.width,"tl",image.data[tl[1]]);
      if (x && weirdRatioFormula(iD[i+PIXEL_OFFSET],iD[i+PIXEL_OFFSET+1],
        iD[i+PIXEL_OFFSET+2],r,g,b)) { tl = [i,x]; return; }

      x=distFromCorner(i,image.width,"br",image.data[br[1]]);
      if (x && weirdRatioFormula(iD[i-PIXEL_OFFSET],iD[i-PIXEL_OFFSET-1],
        iD[i-PIXEL_OFFSET-2],r,g,b)) { br = [i,x]; return; }

      x=distFromCorner(i,image.width,"tr",image.data[tr[1]]);
      if (x && weirdRatioFormula(iD[i-PIXEL_OFFSET],iD[i-PIXEL_OFFSET-1],
        iD[i-PIXEL_OFFSET-2],r,g,b)) { tr = [i,x]; return; }

      x=distFromCorner(i,image.width,"bl",image.data[bl[1]]);
      if (x && weirdRatioFormula(iD[i+PIXEL_OFFSET],iD[i+PIXEL_OFFSET+1],
        iD[i+PIXEL_OFFSET+2],r,g,b)) { bl = [i,x]; return; }
    }
  }

  console.log("tl: "+tl[0]+", br: "+br[0]+", tr: "+tr[0]+", bl: "+bl[0]);
}
function weirdRatioFormula(curR,curG,curB,r,g,b) {
  var count = r+g+b;
  r=!r,g=!g,b=!b;
  return (curR*!r+curG*!g+curB*!b)*(3-count) > (curR*r+curG*g+curB*b)*count;
}
function distFromCorner(pos,w,corner,record) {
  var pos = calculate2DPos(pos,w), dist, direction;
  switch(corner) {
    case "tl": //closest to topleft
      dist = pythag(pos.x,pos.y);
      direction = 1;
      break;
    case "br": //farthest from topleft
      dist = pythag(pos.x,pos.y);
      direction = -1;
    case "tr": //closest to topright
      dist = pythag(w-pos.x,pos.y);
      direction = 1;
    case "bl": //farthest from topright
      dist = pythag(w-pos.x,pos.y);
      direction = -1;
    default:
      return false;
  }
  if (direction * (dist < record)) { //if it's equal it doesn't matter
    return dist;
  }
  return false;
}
function pythag(a,b) {
  return Math.sqrt(a*a+b*b);
}
function calculate2DPos(pos,w) {
  var x = pos % w,
    y = (pos-x)/w;
  return {x:x,y:y};
}
function printImageData(imageData) {
    var canvas = document.createElement("canvas");
    canvas.width = imageData.width;
    canvas.height = imageData.height;

    var ctx = canvas.getContext("2d");

    ctx.putImageData(imageData,0,0);

    document.getElementById("content").appendChild(canvas);
}
</script>
<style type="text/css">
#draw {
  border: 1px solid black;
  position:absolute;
  top: 10px;
  left: 150px;
}
#output {
  position:absolute;
  top: 0;
  left: 0;
}
#input {
  float:right;
  height:240px;
  width:320px;
}
#content {
  position:absolute;
  top:400px;
}
</style>
</head>
<body>
<canvas id="output"></canvas>
<canvas id="draw" width="160" height="120"></canvas>

<video autoplay="autoplay" id="input"></video>
<div id="content">
<button onclick="calibrate()">Show calibration thing</button>
<br />
</div>
</body>
</html>
